package org.ruoyi.dhuman.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.lang.Collections;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.dhuman.config.properties.DhumanProperties;
import org.ruoyi.common.dhuman.utils.FFmpegAudioUtil;
import org.ruoyi.common.dhuman.utils.LocalSubtitleGenerator;
import org.ruoyi.common.dhuman.utils.v1.TTS;
import org.ruoyi.common.dhuman.utils.v1.Video;
import org.ruoyi.common.dhuman.utils.v1.vo.DhumanExpressionVo;
import org.ruoyi.common.dhuman.utils.v1.vo.TTSVo;
import org.ruoyi.common.oss.entity.UploadResult;
import org.ruoyi.dhuman.domain.bo.DhumanBo;
import org.ruoyi.dhuman.domain.bo.DhumanPptBo;
import org.ruoyi.dhuman.domain.bo.DhumanPptListBo;
import org.ruoyi.dhuman.domain.vo.DhumanPptListVo;
import org.ruoyi.dhuman.domain.vo.DhumanPptVo;
import org.ruoyi.dhuman.domain.vo.DhumanVo;
import org.ruoyi.dhuman.enums.GenStatus;
import org.ruoyi.dhuman.utils.CommonUtils;
import org.ruoyi.dhuman.utils.FFmpegUtils;
import org.ruoyi.dhuman.vo.SubInfoVo;
import org.ruoyi.system.service.impl.SysOssServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;

import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

@RequiredArgsConstructor
@Slf4j
@Service
public class GenService {
	private final DhumanProperties dhumanProperties;
	private final IDhumanService dhumanService;
	private final IDhumanPptService dhumanPptService;
	private final IDhumanPptListService dhumanPptListService;
	private final SysOssServiceImpl ossService;
	private LocalSubtitleGenerator generator;

	@PostConstruct
	public void init() {
		if (!dhumanProperties.getKeepGenerate()) {
			return;
		}
		DhumanPptBo bo = new DhumanPptBo();
		bo.setStatus(GenStatus.GENERATING.toString());
		List<DhumanPptVo> list = dhumanPptService.queryList( bo);
		for (DhumanPptVo dhumanPptVo : list) {
			log.info("继续处理【" + dhumanPptVo.getId() + "】{pptId: " + dhumanPptVo.getId() + "}==========================================================================");
			try {
				genVideoWithDhuman(dhumanPptVo.getId());
			} catch (Exception e) {
				e.printStackTrace();
				log.error("处理【" + dhumanPptVo.getId() + "】失败", e);
			}
		}
	}

	public LocalSubtitleGenerator getLocalSubtitleGenerator() {
		if (generator != null) {
			return generator;
		}
		try {
			// 模型文件路径 - 需要根据实际下载的模型路径修改
			String acousticModel = dhumanProperties.getAcousticModel();
			String dictionary = dhumanProperties.getDictionary();
			String languageModel = dhumanProperties.getLanguageModel();

			// 创建字幕生成器
			generator = new LocalSubtitleGenerator(
				acousticModel, dictionary, languageModel
			);

			// 尝试初始化对齐器
			try {
				generator.initialize();
				log.info("语音对齐器初始化成功");
				return generator;
			} catch (Exception e) {
				log.error("语音对齐器初始化失败，将使用时间估算方法", e);
			}
		} catch (Exception e) {
			log.error("初始化语音对齐器失败", e);
		}

		return generator;
	}

	public DhumanPptVo genVideoNoDhuman(Long pptId) {
		// 合成语音
		genTTS(pptId);

		// 合成视频
		String previewVideoUrl = genPreviewVideo(pptId,  false);
		if (StringUtils.isEmpty(previewVideoUrl)) {
			log.info("合成预览视频失败");
			return null;
		}

		// 更新数据
		DhumanPptBo bo = new DhumanPptBo();
		bo.setId(pptId);
		bo.setPreVideoUrl(previewVideoUrl);
		dhumanPptService.updateByBo(bo);

		DhumanPptVo vo = new DhumanPptVo();
		BeanUtils.copyProperties(bo, vo);

		return vo;
	}

	public DhumanPptVo genVideoWithDhuman(Long pptId) {
		DhumanPptVo vo = dhumanPptService.queryById(pptId);
		if (GenStatus.DONE.toString().equals(vo.getStatus())) {
			return vo;
		}

		// 更新状态
		DhumanPptBo bo = new DhumanPptBo();
		bo.setId(pptId);
		bo.setStatus(GenStatus.GENERATING.toString());
		dhumanPptService.updateByBo(bo);

		// 异步执行视频生成任务
		new Thread(() -> {
			try {
				genTTS(pptId);
				GenStatus status = createVideo(pptId);
				if (status == null || status != GenStatus.DONE) {
					bo.setStatus(status == null? GenStatus.GENERATING.toString() : status.toString());
					dhumanPptService.updateByBo(bo);
					return;
				}
				queryVideo(pptId);
				String previewVideoUrl = genVideo(pptId, false);
				bo.setStatus(GenStatus.DONE.toString());
				bo.setVideoUrl(previewVideoUrl);
				dhumanPptService.updateByBo(bo);
			} catch (Exception e) {
				log.error("异步生成视频失败", e);
				bo.setStatus(GenStatus.FAILED.toString());
				dhumanPptService.updateByBo(bo);
			}
		}).start();

		vo.setStatus(GenStatus.GENERATING.toString());
		return vo;
	}

	private void genTTS(Long pptId) {
		DhumanPptListBo dhumanPptListBo = new DhumanPptListBo();
		dhumanPptListBo.setDhumanPptId(pptId);
		List<DhumanPptListVo> list = dhumanPptListService.queryList(dhumanPptListBo);
		if (Collections.isEmpty(list)) {
			return;
		}
		String folderPath = createTempFolderPath(null);
		TTS tts = new TTS(dhumanProperties.getTtsAppid(), dhumanProperties.getTtsAppsecret(), dhumanProperties.getTtsEndpoint());
		for (int i = 0; i < list.size(); i++) {
			DhumanPptListVo dhumanPptListVo = list.get(i);
			log.info("开始处理第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT的语音==========================================================================");
			String subPPTFolderPath = folderPath + "/" + "subPPT_" + i;
			File subFolder = new File(folderPath);
			if (!subFolder.exists()) {
				subFolder.mkdirs();
			}

			try {
				genSubTTS(tts, subPPTFolderPath, dhumanPptListVo);
				log.info("第【\" + i + \"】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT的语音处理成功==========================================================================");
			} catch (Exception e) {
				e.printStackTrace();
				log.error("第【\" + i + \"】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT的语音处理失败==========================================================================", e);
			}

		}
	}

	private void genSubTTS(TTS tts, String subPPTFolderPath, DhumanPptListVo dhumanPptListVo) {
		String copyText = dhumanPptListVo.getCopyText();
		if (StringUtils.isEmpty(copyText)) {
			return;
		}

		if (dhumanPptListVo.getTtsStatus() != null && GenStatus.DONE.toString().equals(dhumanPptListVo.getTtsStatus())) {
			if (StringUtils.isNotEmpty(dhumanPptListVo.getTtsAudioUrl())) {
				return;
			}
		}

		TTSVo requestVO = new TTSVo();
		requestVO.setText(copyText);
		requestVO.setVoice_name("xiaoya");
		requestVO.setWith_caption(1);	// 返回时间线
		String ttsResponse = tts.doTTS(requestVO);
		log.info("TTS Response: " + ttsResponse);

		ObjectMapper mapper = new ObjectMapper();
		JsonNode jsonNode = null;
		try {
			jsonNode = mapper.readTree(ttsResponse);
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}

		DhumanPptListBo bo = new DhumanPptListBo();
		bo.setId(dhumanPptListVo.getId());
		String audioUrl = null;
		if (jsonNode != null && jsonNode.has("code") && null != jsonNode.get("code") && "0".equals(jsonNode.get("code").asText())) {
			if (jsonNode.has("data")) {
				JsonNode dataNode = jsonNode.get("data");
				if (dataNode.has("tts") && null != dataNode.get("tts")) {
					JsonNode ttsNode = dataNode.get("tts");
					if (ttsNode.has("url") && null != ttsNode.get("url")) {
						audioUrl = ttsNode.get("url").asText();
					}
				}
			}
		}
		if (StringUtils.isNotEmpty(audioUrl)) {
			bo.setTtsAudioUrl(audioUrl);
			bo.setTtsStatus(GenStatus.DONE.toString());
		} else {
			bo.setTtsStatus(GenStatus.FAILED.toString());
		}
		// 返回的结果太长，截取前200个字符
		if (ttsResponse.length() > 200) {
			ttsResponse = ttsResponse.substring(0, 200) + "...";
		}
		bo.setTtsResponse(ttsResponse);
		boolean flag = dhumanPptListService.updateByBo(bo);
	}

	private void genSubSrtFile(String audioPath, String copyText, File outputFile) {
		try {
			if (outputFile == null || !outputFile.exists()) {
				outputFile.createNewFile();
			}
			LocalSubtitleGenerator generator = getLocalSubtitleGenerator();
			if (generator != null) {
				generator.generateSubtitles(audioPath, copyText, outputFile.getAbsolutePath());
			}
		} catch (Exception e) {
			log.error("生成字幕文件失败", e);
		}
	}

	private String genPreviewVideo(Long pptId, boolean clearWorkspace) {
		DhumanPptListBo dhumanPptListBo = new DhumanPptListBo();
		dhumanPptListBo.setDhumanPptId(pptId);
		List<DhumanPptListVo> list = dhumanPptListService.queryList(dhumanPptListBo);
		if (Collections.isEmpty(list)) {
			return null;
		}

		String folderPath = createTempFolderPath(null);
//		OssClient storage = OssFactory.instance();
		List<SubInfoVo> subInfoVos = new ArrayList<>();
		for (int i = 0; i < list.size(); i++) {
			DhumanPptListVo dhumanPptListVo = list.get(i);
			log.info("开始合成第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT视频==========================================================================");

			try {
				String subPPTFolderPath = folderPath + "/" + "subPPT_" + i;
				File subFolder = new File(folderPath);
				if (!subFolder.exists()) {
					subFolder.mkdirs();
				}

				SubInfoVo subInfoVo = genSubPreviewVideo(subPPTFolderPath, dhumanPptListVo);
				if (subInfoVo != null) {
					subInfoVos.add(subInfoVo);
				}
				log.info("第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT视频处理成功==========================================================================");
			} catch (Exception e) {
				e.printStackTrace();
				log.error("第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT视频处理失败==========================================================================", e);
			}
		}

		// 合成视频文件
		File despFile = genFilelisto(folderPath, subInfoVos);

		String outputFilePath = folderPath + "/" + "output.mp4";
		boolean success = false;
		try {
//			success = FFmpegUtils.executeConcatCommand(despFile.getAbsolutePath(), outputFilePath);
			success = FFmpegUtils.executeConcatCommand(subInfoVos, outputFilePath, FFmpegUtils.Preset.ultrafast);
		} catch (Exception e) {
			log.error("合并视频生成失败", e);
			throw new RuntimeException("合并视频生成失败", e);
		}

		if (success) {
			log.info("合并视频生成成功！");
		} else {
			log.error("合并视频生成失败！");
			return null;
		}

		// 上传文件
		File outputFile = new File(outputFilePath);
		UploadResult oploadResult = null;
		try {
			oploadResult = CommonUtils.upload(outputFile);
		} catch (Exception e) {
			log.error("上传文件失败: " + outputFile.getAbsolutePath(), e);
			throw new RuntimeException(e);
		}

		// 删除文件
		try {
			if (clearWorkspace) {
				new File(folderPath).delete();
			}
		} catch (Exception e) {
			log.error("删除文件失败: " + outputFile.getAbsolutePath(), e);
		}

		return oploadResult == null? null : oploadResult.getUrl();
	}

	private SubInfoVo genSubPreviewVideo(String subPPTFolderPath, DhumanPptListVo dhumanPptListVo) {
		// 获取数字人表达式
		String dhumanExpression = dhumanPptListVo.getDhumanExpression();
		DhumanExpressionVo dhumanExpressionVo = new DhumanExpressionVo();
		try {
			if (StringUtils.isNotEmpty(dhumanExpression)) {
				dhumanExpressionVo = new ObjectMapper().readValue(dhumanExpression, DhumanExpressionVo.class);
			}
		} catch (Exception e) {
			log.error("数字人表达式格式有误", e);
		}

		// TTS 文件
		String subTTSAudioUrl = dhumanPptListVo.getTtsAudioUrl();
		String subTTSFileName = null;
		if (StringUtils.isNotEmpty(subTTSAudioUrl)) {
			subTTSFileName = CommonUtils.download(subTTSAudioUrl, subPPTFolderPath, "tts_");
		}

		double durationInSeconds = 1.0;
		if (StringUtils.isNotEmpty(subTTSFileName)) {
			File subTTSFile = new File(subPPTFolderPath, subTTSFileName);
			if (subTTSFile != null && subTTSFile.exists()) {
				long durationMilliseconds = FFmpegAudioUtil.getAudioDuration(subTTSFile.getAbsolutePath());
				durationInSeconds = new BigDecimal(durationMilliseconds).divide(new BigDecimal(1000), 3, RoundingMode.CEILING).doubleValue();
			}
		}

		File outputFile = null;

		// 合成视频 Step1: 背景视频
		File outputBaseFile = genSubBaseVideo(dhumanPptListVo, dhumanExpressionVo, durationInSeconds, subPPTFolderPath);
		if (outputBaseFile != null) {
			outputFile = outputBaseFile;
		} else {
			return null;
		}

		// 合成视频 Step2: 数字人图片

		// 数字人图片
		String subDhumanImageFileName = null;
		File outputDhumanFile = new File(subPPTFolderPath, "output_dhuman.mp4");

		if (dhumanExpressionVo != null && StringUtils.isNotEmpty(dhumanExpressionVo.getDhumanUrl())) {
			subDhumanImageFileName = CommonUtils.download(dhumanExpressionVo.getDhumanUrl(), subPPTFolderPath, "dhumanImage_");
			if (StringUtils.isNotEmpty(subDhumanImageFileName)) {
				// 有数字人图片
				try {
					File subDhumanImageFile = new File(subPPTFolderPath, subDhumanImageFileName);
					String[] args = getDefaultConfig(dhumanExpressionVo);
					boolean success = FFmpegUtils.executeConcatImageCommand(outputBaseFile.getAbsolutePath(),
						subDhumanImageFile.getAbsolutePath(), args[2], args[3], outputDhumanFile.getAbsolutePath());
					if (success) {
						outputFile = outputDhumanFile;
					}
				} catch (Exception e) {
					log.error("合成数字人视频失败", e);
				}
			}
		}


		// 合成视频 Step3: 合成字幕
//		dhumanExpressionVo.setHasSubtitle(true);
		File subTTSFile = null;
		if (StringUtils.isNotEmpty(subTTSFileName)) {
			if (new File(subPPTFolderPath, subTTSFileName) != null && new File(subPPTFolderPath, subTTSFileName).exists()) {
				subTTSFile = new File(subPPTFolderPath, subTTSFileName);
			}
		}

		boolean hasTTS = subTTSFile != null && subTTSFile.exists();
		boolean hasSubtitle = dhumanExpressionVo.isHasSubtitle();
		hasSubtitle = hasSubtitle && hasTTS;
		File subSrtFile = new File(subPPTFolderPath, "srt_create.srt");
		if (hasSubtitle) {
			genSubSrtFile(subTTSFile.getAbsolutePath(), dhumanPptListVo.getCopyText(), subSrtFile);
		}
		hasSubtitle = hasSubtitle && subSrtFile != null && subSrtFile.exists();;

		File outputFinalFile = new File(subPPTFolderPath, "output_final.mp4");
		try {
			boolean success = FFmpegUtils.executeTTSCommand(outputFile.getAbsolutePath(),
				hasTTS? subTTSFile.getAbsolutePath() : null,
				hasSubtitle? subSrtFile.getAbsolutePath() : null, "FontName=Microsoft YaHei,FontSize=20,Spacing=1,Outline=0.5,Shadow=0,Alignment=2,MarginV=20",
				outputFinalFile.getAbsolutePath());
			if (success) {
				outputFile = outputFinalFile;
			}
		} catch (Exception e) {
			log.error("合成语音(字幕)视频失败：{pptListId: " + dhumanPptListVo.getId() + "}", e);
		}

		return new SubInfoVo(outputFile, dhumanExpressionVo);
	}

	private File genSubBaseVideo(DhumanPptListVo dhumanPptListVo, DhumanExpressionVo dhumanExpressionVo, double durationInSeconds, String subPPTFolderPath) {
		// ppt 图片
		String subPPTImageUrl = dhumanPptListVo.getImageUrl();
		String subPPTFileName = null;
		if (StringUtils.isNotEmpty(subPPTImageUrl)) {
			subPPTFileName = CommonUtils.download(subPPTImageUrl, subPPTFolderPath, "ppt_");
		}
		if (StringUtils.isEmpty(subPPTFileName)) {
			subPPTFileName = copyDefaultFile("full.png", subPPTFolderPath, "ppt_full.png");
		}


		// 背景照片
		String subBgFileName = null;
		if (dhumanExpressionVo.hasBackground()) {
			// 如果有图片
			if (dhumanExpressionVo != null && StringUtils.isNotEmpty(dhumanExpressionVo.getBgUrl())) {
				subBgFileName = CommonUtils.download(dhumanExpressionVo.getBgUrl(), subPPTFolderPath, "bg_");
			}
			// 如果没有图片或者图片下载失败
			if (StringUtils.isEmpty(subBgFileName)) {
				subBgFileName = copyDefaultFile("full.png", subPPTFolderPath, "bg_full.png");
			}
		}

		// 合成基础背景视频
		File outputBaseFile = new File(subPPTFolderPath, "output_base.mp4");
		String[] args = getDefaultConfig(dhumanExpressionVo);

		boolean success = false;
		try {
			File subPPTFile = new File(subPPTFolderPath, subPPTFileName);
			if (dhumanExpressionVo.hasBackground()) {
				File subBgFile = new File(subPPTFolderPath, subBgFileName);
				success = FFmpegUtils.executeFfmpegBaseCommand(subBgFile.getAbsolutePath(), subPPTFile.getAbsolutePath(), args[0], args[1], durationInSeconds, outputBaseFile.getAbsolutePath());
			} else {
				success = FFmpegUtils.executeFfmpegBaseCommand(subPPTFile.getAbsolutePath(), durationInSeconds, outputBaseFile.getAbsolutePath());
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException("合成基础视频失败", e);
		}

		if (success) {
			return outputBaseFile;
		} else {
			return null;
		}
	}

	private GenStatus createVideo(Long pptId) {
		DhumanPptListBo dhumanPptListBo = new DhumanPptListBo();
		dhumanPptListBo.setDhumanPptId(pptId);
		List<DhumanPptListVo> list = dhumanPptListService.queryList(dhumanPptListBo);
		Video video = new Video(dhumanProperties.getVideoAppid(),
			dhumanProperties.getVideoAappsecret(),
			dhumanProperties.getVideoEndpointCreate(),
			dhumanProperties.getVideoEndpointStatus());

		// 查询所有数字人
		Map<Long, String> dhumanMap = new HashMap<>();
		List<DhumanVo> dhumanList = dhumanService.queryList(new DhumanBo());
		for (DhumanVo dhumanVo : dhumanList) {
			dhumanMap.put(dhumanVo.getId(), dhumanVo.getDhumanModelId());
		}

		for (int i = 0; i < list.size(); i++) {
			DhumanPptListVo dhumanPptListVo = list.get(i);
			log.info("开始创建第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT的数字人视频==========================================================================");

			try {
				GenStatus status = createSubVideo(dhumanPptListVo, video, dhumanMap);
				if (GenStatus.DONE == status) {
					log.info("第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT的数字人视频任务创建成功==========================================================================");
				} else if (GenStatus.FAILED == status) {
					log.error("第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT的数字人视频任务创建失败==========================================================================");
					return status;
				} else {
					log.info("第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT的数字人视频任务无须创建==========================================================================");
				}
			} catch (Exception e) {
				e.printStackTrace();
				log.error("第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT的数字人视频任务创建异常==========================================================================", e);
				return GenStatus.FAILED;
			}
		}

		return GenStatus.DONE;
	}

	private GenStatus createSubVideo(DhumanPptListVo dhumanPptListVo, Video video, Map<Long, String>  dhumanMap) {
		if (GenStatus.DONE.toString().equals(dhumanPptListVo.getDhumanCreateVideoStatus())) {
			return null;
		}

		String audioUrl = dhumanPptListVo.getTtsAudioUrl();
		if (StringUtils.isEmpty(audioUrl)) {
			return null;
		}

		String dhumanExpression = dhumanPptListVo.getDhumanExpression();
		DhumanExpressionVo dhumanExpressionVo = new DhumanExpressionVo();
		try {
			if (StringUtils.isNotEmpty(dhumanExpression)) {
				dhumanExpressionVo = new ObjectMapper().readValue(dhumanExpression, DhumanExpressionVo.class);
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.error("数字人表达式格式有误", e);
		}

		String dhumanModelId = dhumanMap.get(dhumanExpressionVo.getDhumanId());
		if (StringUtils.isEmpty(dhumanModelId)) {
			log.info("无须合成数字人视频");
			return null;
		}

		String videoResponse = video.createVideo(audioUrl, dhumanModelId, null, null);

		log.info("Video Response: " + videoResponse);

		ObjectMapper mapper = new ObjectMapper();
		JsonNode jsonNode = null;
		try {
			jsonNode = mapper.readTree(videoResponse);
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}

		DhumanPptListBo bo = new DhumanPptListBo();
		bo.setId(dhumanPptListVo.getId());
		bo.setDhumanCreateVideoStartTime(new Date());
		String videoId = null;
		if (jsonNode != null && jsonNode.has("code") && null != jsonNode.get("code") && "0".equals(jsonNode.get("code").asText())) {
			if (jsonNode.has("data")) {
				JsonNode dataNode = jsonNode.get("data");
				if (dataNode.has("video_id") && null != dataNode.get("video_id")) {
					videoId = dataNode.get("video_id").asText();
				}
			}
		}

		GenStatus status = null;
		if (StringUtils.isNotEmpty(videoId)) {
			bo.setDhumanVideoId(videoId);
			status = GenStatus.DONE;
		} else {
			status = GenStatus.FAILED;
		}
		bo.setDhumanCreateVideoStatus(status.toString());
		// 返回的结果太长，截取前200个字符
		if (StringUtils.isNotEmpty(videoResponse) && videoResponse.length() > 200) {
			videoResponse = videoResponse.substring(0, 200) + "...";
		}
		bo.setDhumanCreateVideoResponse(videoResponse);

		boolean flag = dhumanPptListService.updateByBo(bo);

		return status;
	}

	private void queryVideo(Long pptId) {
		DhumanPptListBo dhumanPptListBo = new DhumanPptListBo();
		dhumanPptListBo.setDhumanPptId(pptId);
		List<DhumanPptListVo> list = dhumanPptListService.queryList(dhumanPptListBo);
		Video video = new Video(dhumanProperties.getVideoAppid(),
			dhumanProperties.getVideoAappsecret(),
			dhumanProperties.getVideoEndpointCreate(),
			dhumanProperties.getVideoEndpointStatus());

		for (int i = 0; i < list.size(); i++) {
			DhumanPptListVo dhumanPptListVo = list.get(i);
			log.info("查询第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT的数字人视频==========================================================================");

			try {
				querySubVideo(dhumanPptListVo, video);
				log.info("第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT的数字人视频任务查询成功==========================================================================");
			} catch (Exception e) {
				e.printStackTrace();
				log.error("第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT的数字人视频任务查询失败==========================================================================", e);
			}
		}
	}

	private void querySubVideo(DhumanPptListVo dhumanPptListVo, Video video) {
		if (!GenStatus.DONE.toString().equals(dhumanPptListVo.getDhumanCreateVideoStatus())) {
			return;
		}

		while (true) {
			String dhumanVideoId = dhumanPptListVo.getDhumanVideoId();

			String videoResponse = video.queryVideo(dhumanVideoId);

			log.info("Video Response: " + videoResponse);

			JsonNode jsonNode = null;
			try {
				jsonNode = new ObjectMapper().readTree(videoResponse);
			} catch (JsonProcessingException e) {
				e.printStackTrace();
			}

			DhumanPptListBo bo = new DhumanPptListBo();
			bo.setId(dhumanPptListVo.getId());
			bo.setDhumanResponse(videoResponse);
			String dhumanStatus = null;
			if (jsonNode != null && jsonNode.has("code") && null != jsonNode.get("code") && "0".equals(jsonNode.get("code").asText())) {
				if (jsonNode.has("data")) {
					JsonNode dataNode = jsonNode.get("data");
					if (dataNode.has("status") && null != dataNode.get("status")) {
						dhumanStatus = dataNode.get("status").asText();
					}
				}
			}
			// 任务状态码：0=未开始，1=预处理，2=处理中，3=后处理，4=成功，5=失败，6=超时。
			if ("4".equals(dhumanStatus)) {
				try {
					bo.setDhumanVideoUrl(jsonNode.get("data").get("video_url").asText());
					bo.setDhumanVideoMaskUrl(jsonNode.get("data").get("video_mask_url").asText());
				} catch (Exception e) {
					e.printStackTrace();
				}
				bo.setDhumanCreateVideoEndTime(new Date());
				bo.setDhumanStatus(GenStatus.DONE.toString());
				boolean flag = dhumanPptListService.updateByBo(bo);
				break;
			} else if ("5".equals(dhumanStatus)) {
				bo.setDhumanStatus(GenStatus.FAILED.toString());
				boolean flag = dhumanPptListService.updateByBo(bo);
				break;
			} else if ("6".equals(dhumanStatus)) {
				bo.setDhumanStatus(GenStatus.TIMEOOUT.toString());
				boolean flag = dhumanPptListService.updateByBo(bo);
				break;
			} else {
				bo.setDhumanStatus(GenStatus.GENERATING.toString());
				boolean flag = dhumanPptListService.updateByBo(bo);

				// 阻塞
				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

	private String genVideo(Long pptId, boolean clearWorkspace) {
		DhumanPptListBo dhumanPptListBo = new DhumanPptListBo();
		dhumanPptListBo.setDhumanPptId(pptId);
		List<DhumanPptListVo> list = dhumanPptListService.queryList(dhumanPptListBo);
		if (Collections.isEmpty(list)) {
			return null;
		}

		String folderPath = createTempFolderPath(null);
//		OssClient storage = OssFactory.instance();
		List<SubInfoVo> subInfoVos = new ArrayList<>();
		for (int i = 0; i < list.size(); i++) {
			DhumanPptListVo dhumanPptListVo = list.get(i);
			log.info("开始合成第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT视频==========================================================================");

			try {
				String subPPTFolderPath = folderPath + "/" + "subPPT_" + i;
				File subFolder = new File(folderPath);
				if (!subFolder.exists()) {
					subFolder.mkdirs();
				}

				SubInfoVo subInfoVo = genSubVideo(subPPTFolderPath, dhumanPptListVo);
				if (subInfoVo != null) {
					subInfoVos.add(subInfoVo);
				}
				log.info("第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT视频处理成功==========================================================================");
			} catch (Exception e) {
				e.printStackTrace();
				log.error("第【" + i + "】{subPPTId: " + dhumanPptListVo.getId() + "}条PPT视频处理失败==========================================================================", e);
			}
		}

		// 合成视频文件
		File despFile = genFilelisto(folderPath, subInfoVos);

		String outputFilePath = folderPath + "/" + "output.mp4";
		boolean success = false;
		try {
//			success = FFmpegUtils.executeConcatCommand(despFile.getAbsolutePath(), outputFilePath);
			success = FFmpegUtils.executeConcatCommand(subInfoVos, outputFilePath, FFmpegUtils.Preset.medium);
			log.info("合并视频生成成功！");
		} catch (Exception e) {
			log.error("合并视频生成失败", e);
			throw new RuntimeException("合并视频生成失败", e);
		}
		if (success) {
			log.info("视频合成成功！");
		} else {
			log.error("合并视频生成失败！");
			return null;
		}

		// 上传文件
		File outputFile = new File(outputFilePath);
		UploadResult oploadResult = null;
		try {
			oploadResult = CommonUtils.upload(outputFile);
		} catch (Exception e) {
			log.error("上传文件失败: " + outputFile.getAbsolutePath(), e);
			throw new RuntimeException(e);
		}

		// 删除文件
		try {
			if (clearWorkspace) {
				new File(folderPath).delete();
			}
		} catch (Exception e) {
			log.error("删除文件失败: " + outputFile.getAbsolutePath(), e);
		}

		return oploadResult == null? null : oploadResult.getUrl();
	}

	private SubInfoVo genSubVideo(String subPPTFolderPath, DhumanPptListVo dhumanPptListVo) {
		// 获取数字人表达式
		String dhumanExpression = dhumanPptListVo.getDhumanExpression();
		DhumanExpressionVo dhumanExpressionVo = new DhumanExpressionVo();
		try {
			if (StringUtils.isNotEmpty(dhumanExpression)) {
				dhumanExpressionVo = new ObjectMapper().readValue(dhumanExpression, DhumanExpressionVo.class);
			}
		} catch (Exception e) {
			log.error("数字人表达式格式有误", e);
		}

		// TTS 文件
		String subTTSAudioUrl = dhumanPptListVo.getTtsAudioUrl();
		String subTTSFileName = null;
		if (StringUtils.isNotEmpty(subTTSAudioUrl)) {
			subTTSFileName = CommonUtils.download(subTTSAudioUrl, subPPTFolderPath, "tts_");
		}
		
		double durationInSeconds = 1.0;
		if (StringUtils.isNotEmpty(subTTSFileName)) {
			File subTTSFile = new File(subPPTFolderPath, subTTSFileName);
			if (subTTSFile != null && subTTSFile.exists()) {
				long durationMilliseconds = FFmpegAudioUtil.getAudioDuration(subTTSFile.getAbsolutePath());
				durationInSeconds = new BigDecimal(durationMilliseconds).divide(new BigDecimal(1000), 3, RoundingMode.CEILING).doubleValue();
			}
		}
		
		File outputFile = null;

		// 合成视频 Step1: 背景视频
		File outputBaseFile = genSubBaseVideo(dhumanPptListVo, dhumanExpressionVo, durationInSeconds, subPPTFolderPath);
		if (outputBaseFile != null) {
			outputFile = outputBaseFile;
		} else {
			return null;
		}


		// 数字人视频
		String subDhumanVideoFileName = null;
		String subDhumanVideoMaskFileName = null;
		if (GenStatus.DONE.toString().equals(dhumanPptListVo.getDhumanCreateVideoStatus()) && StringUtils.isNotEmpty(dhumanPptListVo.getDhumanVideoUrl())) {
			subDhumanVideoFileName = CommonUtils.download(dhumanPptListVo.getDhumanVideoUrl(), subPPTFolderPath, "dhuman_video_");
			File subDhumanVideoFile = new File(subPPTFolderPath, subDhumanVideoFileName);
			if (!subDhumanVideoFile.exists()) {
				throw new RuntimeException("数字人视频下载失败: " + subDhumanVideoFile.getAbsolutePath());
			}
			subDhumanVideoMaskFileName = CommonUtils.download(dhumanPptListVo.getDhumanVideoMaskUrl(), subPPTFolderPath, "dhuman_video_mask_");
			File subDhumanVideoMaskFile = new File(subPPTFolderPath, subDhumanVideoMaskFileName);
			if (!subDhumanVideoMaskFile.exists()) {
				throw new RuntimeException("数字人蒙版视频下载失败: " + subDhumanVideoMaskFile.getAbsolutePath());
			}
		}


		// 判断TTS文件是否存在
		File subTTSFile = null;
		if (StringUtils.isNotEmpty(subTTSFileName)) {
			if (new File(subPPTFolderPath, subTTSFileName) != null && new File(subPPTFolderPath, subTTSFileName).exists()) {
				subTTSFile = new File(subPPTFolderPath, subTTSFileName);
			}
		}

		if (StringUtils.isNotEmpty(subDhumanVideoFileName) && StringUtils.isNotEmpty(subDhumanVideoMaskFileName)) {
			File subDhumanVideoFile = new File(subPPTFolderPath, subDhumanVideoFileName);
			File subDhumanVideoMaskFile = new File(subPPTFolderPath, subDhumanVideoMaskFileName);
			// 情形一：有数字人（无须声音）
			try {
				File outputDhumanFile = new File(subPPTFolderPath, "output_dhuman.mp4");
				String[] args = getDefaultConfig(dhumanExpressionVo);
				boolean success = FFmpegUtils.executeFfmpegDhumanCommand(outputFile.getAbsolutePath(), subDhumanVideoFile.getAbsolutePath(), subDhumanVideoMaskFile.getAbsolutePath(), args[2], args[3], outputDhumanFile.getAbsolutePath());
				if (success) {
					outputFile = outputDhumanFile;
				}
			} catch (Exception e) {
				throw new RuntimeException("合成数字人视频失败", e);
			}
		} else if (StringUtils.isNotEmpty(subTTSFileName) && subTTSFile != null && subTTSFile.exists()) {
			// 情形二：有声音
			try {
				File outputTTSFile = new File(subPPTFolderPath, "output_tts.mp4");
				boolean success = FFmpegUtils.executeFfmpegTTSCommand(outputFile.getAbsolutePath(), subTTSFile.getAbsolutePath(), outputTTSFile.getAbsolutePath());
				if (success) {
					outputFile = outputTTSFile;
				}
			} catch (Exception e) {
				throw new RuntimeException("合成语音视频失败: " + e.getMessage());
			}
		} else {
			// 情形三：无数字人，无声音
			return new SubInfoVo(outputFile, dhumanExpressionVo);
		}


		// 合成视频 Step3: 合成字幕
//		dhumanExpressionVo.setHasSubtitle(true);
		boolean hasSubtitle = dhumanExpressionVo.isHasSubtitle();
		hasSubtitle = hasSubtitle && subTTSFile != null && subTTSFile.exists();
		File subSrtFile = new File(subPPTFolderPath, "srt_create.srt");
		if (hasSubtitle) {
			genSubSrtFile(subTTSFile.getAbsolutePath(), dhumanPptListVo.getCopyText(), subSrtFile);
			hasSubtitle = hasSubtitle && subSrtFile != null && subSrtFile.exists();
			if (hasSubtitle) {
				File outputFinalFile = new File(subPPTFolderPath, "output_final.mp4");
				try {
					boolean success = FFmpegUtils.executeZimuCommand(outputFile.getAbsolutePath(),
						subSrtFile.getAbsolutePath(), "FontName=Microsoft YaHei,FontSize=20,Spacing=1,Outline=0.5,Shadow=0,Alignment=2,MarginV=20",
						outputFinalFile.getAbsolutePath());
					if (success) {
						outputFile = outputFinalFile;
					}
				} catch (Exception e) {
					log.error("合成语音(字幕)视频失败：{pptListId: " + dhumanPptListVo.getId() + "}", e);
				}
			}
		}

		return new SubInfoVo(outputFile, dhumanExpressionVo);
	}

	private String createTempFolderPath(String name) {
		String workpath = dhumanProperties.getWorkpath();
		String timestamp = new java.text.SimpleDateFormat("yyyyMMdd.HHmmss.SSS").format(new java.util.Date());
		String folderPath = workpath + "/vedio_" + timestamp;

		if (StringUtils.isNotEmpty(name)) {
			folderPath = workpath + "/" + timestamp + "_" + name;
		}

		File folder = new File(folderPath);
		if (!folder.exists()) {
			folder.mkdirs();
		}
		return folderPath;
	}

	private String copyDefaultFile(String sourceFileName, String destFolderPath, String destFileName) {
		InputStream inputStream = getClass().getClassLoader().getResourceAsStream(sourceFileName);
		try {
			File targetFile = new File(destFolderPath, destFileName);
			java.nio.file.Files.copy(
				inputStream,
				targetFile.toPath(),
				java.nio.file.StandardCopyOption.REPLACE_EXISTING
			);
			inputStream.close();
			return destFileName;
		} catch (Exception e) {
			log.error("复制默认背景图片失败", e);
			throw new RuntimeException("复制" + destFileName + "文件失败");
		}
	}

	private String[] getDefaultConfig(DhumanExpressionVo dhumanExpressionVo) {
		String[] defaultConfig = new String[]{"1600:900", "0:0", "400:800", "0:0"};
		if (dhumanExpressionVo == null) {
			return defaultConfig;
		}

		Integer pptW = dhumanExpressionVo.getPptW();
		Integer pptH = dhumanExpressionVo.getPptH();
		Float pptX = dhumanExpressionVo.getPptX();
		Float pptY = dhumanExpressionVo.getPptY();
		String pptArea = (pptW == null || pptH == null) ? defaultConfig[0] : pptW + ":" + pptH;
		String pptXY = (pptX == null || pptY == null) ? defaultConfig[1] : pptX + ":" + pptY;

		Integer dhumanW = dhumanExpressionVo.getDhumanW();
		Integer dhumanH = dhumanExpressionVo.getDhumanH();
		Float dhumanX = dhumanExpressionVo.getDhumanX();
		Float dhumanY = dhumanExpressionVo.getDhumanY();
		String dhumanArea = (dhumanW == null || dhumanH == null) ? defaultConfig[2] : dhumanW + ":" + dhumanH;
		String dhumanXY = (dhumanX == null || dhumanY == null) ? defaultConfig[3] : dhumanX + ":" + dhumanY;

		return new String[]{pptArea, pptXY, dhumanArea, dhumanXY};
	}

	private File genFilelisto(String folderPath, List<SubInfoVo> subInfoVos) {
		String filelistFilePath = folderPath + "/" + "filelist.txt";

		File filelistFile = new File(filelistFilePath);
		try {
			if (!filelistFile.exists()) {
				filelistFile.createNewFile();
			}
		} catch (IOException e) {

		}

		try {
			PrintWriter writer = new PrintWriter(new FileWriter(filelistFilePath));
			for (SubInfoVo vo : subInfoVos) {
				File file = vo.getFile();
				writer.println("file '" + file.getAbsolutePath() + "'");
			}
			writer.flush();
			writer.close();
		} catch (IOException e) {
			log.error("创建文件列表失败", e);
//			throw new RuntimeException("创建文件列表失败", e);
		}

		return filelistFile;
	}
}
